﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Skugo.Core;
using Skugo.Shared;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Media.Imaging;
using System.Windows;
using System.Threading;
using System.Windows.Threading;
using System.Windows.Input;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Windows.Interop;
using System.Windows.Forms.Integration;
using System.Diagnostics;
using System.Windows.Media;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using AvalonDock;
using System.ComponentModel;

namespace Skugo.StandardPlugins
{
    public class Tree : DockablePlugin
    {
        /// <summary>
        /// The type of message we send or receieve.
        /// </summary>
        enum MessageType : byte
        {
            TreeSnapshot,
            ItemSelected,
            ItemDoubleClicked,
            ItemMoved,
            ItemRenamed,
            SubTreeExpanded,
            AddContextMenuItem,
            ContextCommandExecuted,
        }

        private TreeForm Form;

        private ObservableCollection<TreeNode> Roots = null;

        internal Int32 TreeId = 0;

        protected override void Initialize(BinaryReader reader)
        {
            base.Initialize(reader);

            this.Form = new TreeForm(this);
            this.Content = this.Form;

            this.Form.View.SelectedItemChanged += this.OnSelectedItemChanged;
            this.Form.ItemDoubledClicked += this.OnMouseDoubleClick;
        }

        void OnMouseDoubleClick(Object sender, MouseButtonEventArgs e)
        {
            if (this.Form.View.SelectedItem != null)
            {
                // Create a memory stream to write data to
                MemoryStream stream = new MemoryStream();

                // Create a binary writer to write the data
                BinaryWriter outgoing = new BinaryWriter(stream);

                // Write out a the ready message
                outgoing.Write((Byte)MessageType.ItemDoubleClicked);
                outgoing.Write(this.TreeId);
                outgoing.Write(((TreeNode)this.Form.View.SelectedItem).Index);

                this.Send(stream.ToArray());
            }
        }

        void OnSelectedItemChanged(Object sender, RoutedPropertyChangedEventArgs<Object> e)
        {
            // Create a memory stream to write data to
            MemoryStream stream = new MemoryStream();

            // Create a binary writer to write the data
            BinaryWriter outgoing = new BinaryWriter(stream);

            // Write out a the ready message
            outgoing.Write((Byte)MessageType.ItemSelected);
            outgoing.Write(this.TreeId);
            outgoing.Write(((TreeNode)this.Form.View.SelectedItem).Index);

            this.Send(stream.ToArray());
        }

        private ObservableCollection<TreeNode> ReadNodes(BinaryReader incoming, ref Int32 nodeCount)
        {
            var childrenCount = incoming.ReadInt32();

            if (childrenCount == 0)
            {
                return null;
            }

            var children = new ObservableCollection<TreeNode>();

            for (Int32 i = 0; i < childrenCount; ++i)
            {
                var node = new TreeNode();
                node.Name = incoming.ReadAsciiString();
                var icon = (Shared.Icon)incoming.ReadByte();
                node.Icon = IconLoader.Load(icon);
                node.Index = nodeCount;
                ++nodeCount;
                node.Children = this.ReadNodes(incoming, ref nodeCount);
                children.Add(node);
            }

            return children;
        }

        private static void MergeTrees(ref ObservableCollection<TreeNode> oldChildren, TreeNode oldParent, ref ObservableCollection<TreeNode> newChildren, TreeNode newParent)
        {
            if (newChildren == null)
            {
                oldChildren = null;
                TreeNode.Refresh(oldParent);
                return;
            }
            else if (oldChildren == null)
            {
                oldChildren = newChildren;
                TreeNode.Refresh(oldParent);
                return;
            }

            while (oldChildren.Count > newChildren.Count)
            {
                oldChildren.RemoveAt(oldChildren.Count - 1);
            }

            for (Int32 i = 0; i < newChildren.Count; ++i)
            {
                var newNode = newChildren[i];

                if (i == oldChildren.Count)
                {
                    oldChildren.Add(newNode);
                    continue;
                }
                else
                {
                    var oldNode = oldChildren[i];
                    oldNode.Name = newNode.Name;
                    oldNode.Icon = newNode.Icon;
                    oldNode.Index = newNode.Index;

                    MergeTrees(ref oldNode.ChildrenInternal, oldNode, ref newNode.ChildrenInternal, newNode);
                }
            }

            TreeNode.Refresh(oldParent);
        }

        protected override void OnReceive(Byte[] data)
        {
            var incoming = data.ToBinaryReader();

            var messageType = (MessageType)incoming.ReadByte();

            switch (messageType)
            {
                case MessageType.TreeSnapshot:
                {
                    Int32 nodeCount = 0;
                    ++this.TreeId;

                    var newRoots = this.ReadNodes(incoming, ref nodeCount);

                    if (this.Roots != null)
                    {
                        MergeTrees(ref this.Roots, null, ref newRoots, null);
                    }
                    else
                    {
                        this.Roots = newRoots;
                        this.Form.View.ItemsSource = this.Roots;
                    }
                    break;
                }

                case MessageType.AddContextMenuItem:
                {
                    var name = incoming.ReadAsciiString();
                    if (this.Form.View.ContextMenu == null)
                    {
                        this.Form.View.ContextMenu = new ContextMenu();
                    }
                    var item = new MenuItem();
                    var index = this.Form.View.ContextMenu.Items.Count;
                    item.Name = name;
                    item.Header = name;
                    this.Form.View.ContextMenu.Items.Add(item);


                    item.Click += (sender, e) =>
                    {
                        // Create a memory stream to write data to
                        MemoryStream stream = new MemoryStream();

                        // Create a binary writer to write the data
                        BinaryWriter outgoing = new BinaryWriter(stream);
                        
                        // Write out a the ready message
                        outgoing.Write((Byte)MessageType.ContextCommandExecuted);
                        outgoing.Write(this.TreeId);
                        outgoing.Write(((TreeNode)this.Form.View.SelectedItem).Index);
                        outgoing.Write(index);

                        this.Send(stream.ToArray());
                    };

                    break;
                }

                default:
                    throw new Exception("Unhandled message type!");
            }
        }
    }


    public class TreeNode : INotifyPropertyChanged
    {
        public String Name { get; set; }
        public BitmapSource Icon { get; set; }
        public ObservableCollection<TreeNode> Children
        {
            get { return this.ChildrenInternal; }
            set { this.ChildrenInternal = value; }
        }
        public Int32 Index;

        internal ObservableCollection<TreeNode> ChildrenInternal;

        public Byte[] Serialize(Int32 treeId)
        {
            // Create a memory stream to write data to
            MemoryStream stream = new MemoryStream();

            // Create a binary writer to write the data
            BinaryWriter outgoing = new BinaryWriter(stream);

            outgoing.Write(treeId);
            outgoing.Write(this.Index);

            return stream.ToArray();
        }

        internal static void Refresh(TreeNode node)
        {
            if (node != null && node.PropertyChanged != null)
            {
                node.PropertyChanged(node, new PropertyChangedEventArgs(String.Empty));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}
